{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b3d3c87a",
   "metadata": {},
   "source": [
    "example selectors 示例选择器，先决条件：Chat models, Few-shot prompting。\n",
    "\n",
    "在 LangChain v0.3 版本中，Example Selectors（示例选择器）是用于动态筛选少样本示例的组件，核心作用是：根据用户输入的内容，从示例库中自动选择最相关、最有效的示例，与提示词模板结合生成更精准的输入，从而提升大模型在特定任务上的表现。\n",
    "\n",
    "与固定使用所有示例的 FewShotPromptTemplate 不同，Example Selectors 能根据输入动态调整示例，避免冗余信息干扰模型，尤其适合示例库庞大或输入内容差异较大的场景（如嵌入式开发中 “不同芯片的外设配置”“多样化的故障诊断” 等任务）。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca4f5226",
   "metadata": {},
   "source": [
    "# 一，Example Selectors 的核心价值\n",
    "在少样本学习（Few-shot Learning）中，示例的质量和相关性直接影响模型输出。Example Selectors 的核心优势体现在：\n",
    "\n",
    "1. **动态适配输入：** 根据用户当前输入的语义、长度或领域，自动筛选最相关的示例（如用户问 “STM32 的 I2C 配置”，自动选择 I2C 相关示例，而非 SPI 示例）。\n",
    "2. **控制提示长度：** 避免一次性加入过多示例导致提示词过长（超出模型上下文窗口），通过选择最必要的示例平衡信息量与长度。\n",
    "3. **提升任务一致性：** 对同类任务（如 “芯片寄存器解析”），确保选择的示例格式统一，引导模型输出符合预期的结果。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e435547e",
   "metadata": {},
   "source": [
    "# 二，常用的 Example Selectors 类型\n",
    "LangChain v0.3 提供了多种示例选择器，适配不同筛选逻辑，以下是嵌入式开发场景中常用的类型：\n",
    "\n",
    "## 1. SemanticSimilarityExampleSelector（语义相似性选择器）\n",
    "**核心逻辑：** 通过计算用户输入与示例的语义相似度（基于嵌入模型），选择最相似的 N 个示例。\n",
    "\n",
    "**适用场景：** 需要根据输入内容的语义关联选择示例（如 “根据用户提问的外设类型选择对应配置示例”）。\n",
    "\n",
    "**关键参数：**\n",
    "  - examples：示例库（列表，每个元素为包含 “输入 - 输出” 的字典）。\n",
    "  - embeddings：用于计算语义相似度的嵌入模型（如 OpenAIEmbeddings、HuggingFaceEmbeddings）。\n",
    "  - vectorstore：存储示例向量的向量数据库（如 FAISS，自动创建）。\n",
    "  - k：选择的示例数量（默认 4）。\n",
    "\n",
    "## 2. LengthBasedExampleSelector（长度基于选择器）\n",
    "**核心逻辑：** 根据用户输入的长度动态调整示例数量（输入越短，选择越少的示例；输入越长，可容纳更多示例）。\n",
    "\n",
    "**适用场景：** 输入长度差异大的任务（如 “短问题用 1 个示例，长文档解析用 3 个示例”），避免提示词总长度超限。\n",
    "\n",
    "**关键参数：**\n",
    "- examples：示例库。\n",
    "- example_prompt：示例的提示模板（用于计算单个示例的长度）。\n",
    "- max_length：提示词允许的最大长度（包括示例、用户输入和模板其他内容）。\n",
    "\n",
    "## 3. MaxMarginalRelevanceExampleSelector（最大边际相关性选择器 MMR）\n",
    "**核心逻辑：** 在保证与输入语义相似的同时，选择多样性更高的示例（避免示例内容重复）。\n",
    "\n",
    "**适用场景：** 需要覆盖多种情况的任务（如 “嵌入式故障诊断”，既需要与当前故障相似的示例，也需要不同故障类型的参考示例）。\n",
    "\n",
    "**关键参数：**\n",
    "- 继承 SemanticSimilarityExampleSelector 的参数（需嵌入模型和向量库）。\n",
    "- lambda_mult：控制多样性的权重（0 表示只考虑相似度，1 表示只考虑多样性）。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2ce8856",
   "metadata": {},
   "source": [
    "# 示例\n",
    "## LengthBasedExampleSelector 示例\n",
    "基于最大长度就是 LengthBasedExampleSelector 类中的 max_length 参数。它的值越大，所能trace的示例就越多。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e6ae8209",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate\n",
    "from langchain_core.example_selectors import LengthBasedExampleSelector\n",
    "\n",
    "# 创建反义词示例集\n",
    "examples = [\n",
    "    {\"input\": \"happy\", \"output\": \"sad\"},\n",
    "    {\"input\": \"tall\", \"output\": \"short\"},\n",
    "    {\"input\": \"energetic\", \"output\": \"lethargic\"},\n",
    "    {\"input\": \"sunny\", \"output\": \"gloomy\"},\n",
    "    {\"input\": \"windy\", \"output\": \"calm\"},\n",
    "]\n",
    "\n",
    "example_prompt = PromptTemplate(\n",
    "    input_variables = [\"input\", \"output\"],\n",
    "    template=\"输入: {input}\\n输出: {output}\\n\",\n",
    ")\n",
    "\n",
    "example_selector = LengthBasedExampleSelector(\n",
    "    examples = examples,\n",
    "    example_prompt = example_prompt,\n",
    "    max_length = 25,\n",
    "    # The function used to get the length of a string, which is used\n",
    "    # to determine which examples to include. It is commented out because\n",
    "    # it is provided as a default value if none is specified.\n",
    "    # get_text_length: Callable[[str], int] = lambda x: len(re.split(\"\\n| \", x))    \n",
    ")\n",
    "\n",
    "dynamic_prompt = FewShotPromptTemplate(\n",
    "    # 使用示例选择器代替examples\n",
    "    example_selector = example_selector,\n",
    "    # 用于格式化示例的PromptTemplate。\n",
    "    example_prompt = example_prompt,\n",
    "    # 提示模板的输入变量\n",
    "    input_variables = [\"adjective\"],\n",
    "    # 提示模板的模板\n",
    "    prefix = \"请将输入的词转换为其反义词。\\n\",\n",
    "    suffix=\"输入: {adjective}\\n输出:\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "4815bb0d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "请将输入的词转换为其反义词。\n",
      "\n",
      "\n",
      "输入: happy\n",
      "输出: sad\n",
      "\n",
      "\n",
      "输入: tall\n",
      "输出: short\n",
      "\n",
      "\n",
      "输入: energetic\n",
      "输出: lethargic\n",
      "\n",
      "\n",
      "输入: sunny\n",
      "输出: gloomy\n",
      "\n",
      "\n",
      "输入: big\n",
      "输出:\n"
     ]
    }
   ],
   "source": [
    "# 使用一个小的输入，这样的话它可以选择更多的示例 (因为我们限制了长度为25)\n",
    "filled_prompt = dynamic_prompt.format(adjective = \"big\")\n",
    "print(filled_prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "0969ae63",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "请将输入的词转换为其反义词。\n",
      "\n",
      "\n",
      "输入: happy\n",
      "输出: sad\n",
      "\n",
      "\n",
      "输入: big and huge and massive and large and gigantic and tall and much bigger than everything else\n",
      "输出:\n"
     ]
    }
   ],
   "source": [
    "# 使用一个更长的输入，使得选择器只能选择更少的示例\n",
    "long_string = \"big and huge and massive and large and gigantic and tall and much bigger than everything else\"\n",
    "filled_prompt = dynamic_prompt.format(adjective=long_string)\n",
    "print(filled_prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "79d79dd4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "请将输入的词转换为其反义词。\n",
      "\n",
      "\n",
      "输入: happy\n",
      "输出: sad\n",
      "\n",
      "\n",
      "输入: tall\n",
      "输出: short\n",
      "\n",
      "\n",
      "输入: energetic\n",
      "输出: lethargic\n",
      "\n",
      "\n",
      "输入: sunny\n",
      "输出: gloomy\n",
      "\n",
      "\n",
      "输入: enthusiastic\n",
      "输出:\n"
     ]
    }
   ],
   "source": [
    "# 可以动态的向示例选择器中添加示例\n",
    "new_example = {\"input\": \"big\", \"output\": \"small\"}\n",
    "dynamic_prompt.example_selector.add_example(new_example)\n",
    "filled_prompt = dynamic_prompt.format(adjective=\"enthusiastic\")\n",
    "print(filled_prompt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b341b07b",
   "metadata": {},
   "source": [
    "因为上面我们只是把提示词打印出来，并没有传入给大模型；如果给到大模型后，那它肯定可以正常的输出给定词的反义词了。如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "d82d2634",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "输入: enthusiastic  \n",
      "输出: apathetic\n"
     ]
    }
   ],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "import os\n",
    "\n",
    "api_base = os.getenv(\"OPENAI_API_BASE\")\n",
    "api_key = os.getenv(\"OPENAI_API_KEY\")\n",
    "\n",
    "# 初始化 DeepSeek 模型（需配置 API 密钥）\n",
    "llm = ChatOpenAI(\n",
    "    model_name=\"deepseek-chat\",  # 或 deepseek-coder 用于代码生成\n",
    "    openai_api_base=api_base,\n",
    "    openai_api_key=api_key,\n",
    "    temperature=0  # 控制输出随机性（0 表示更严谨）\n",
    ")\n",
    "\n",
    "print(llm.invoke(filled_prompt).content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd4779b4",
   "metadata": {},
   "source": [
    "## SemanticSimilarityExampleSelector 示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "200a2a88",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install langchain_chroma"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "9e562f4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_chroma import Chroma\n",
    "from langchain_core.example_selectors import SemanticSimilarityExampleSelector\n",
    "from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate\n",
    "from langchain_openai import OpenAIEmbeddings\n",
    "\n",
    "example_prompt = PromptTemplate(\n",
    "    input_variables=[\"input\", \"output\"],\n",
    "    template=\"输入: {input}\\n输出: {output}\",\n",
    ")\n",
    "\n",
    "# Examples of a pretend task of creating antonyms.\n",
    "examples = [\n",
    "    {\"input\": \"happy\", \"output\": \"sad\"},\n",
    "    {\"input\": \"tall\", \"output\": \"short\"},\n",
    "    {\"input\": \"energetic\", \"output\": \"lethargic\"},\n",
    "    {\"input\": \"sunny\", \"output\": \"gloomy\"},\n",
    "    {\"input\": \"windy\", \"output\": \"calm\"},\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "191ae32a",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install langchain-huggingface sentence-transformers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56d36f20",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_huggingface import HuggingFaceEmbeddings\n",
    "\n",
    "# 使用通用开源嵌入模型(免费)，deepseek未提供嵌入模型，所以我们选择HuggingFace的开源模型\n",
    "embeddings = HuggingFaceEmbeddings(\n",
    "    model_name = \"../../embed_model/bge-large-zh-v1.5\",  # 首次会下载该模型（这里我们提前手动下载好了）\n",
    "    model_kwargs = {\"device\": \"cpu\"},  # 可指定\"cuda\"使用GPU,\n",
    "    encode_kwargs = {\"normalize_embeddings\": True}  # 归一化向量，提升相似度计算准确性\n",
    ")\n",
    "\n",
    "example_selector = SemanticSimilarityExampleSelector.from_examples(\n",
    "    examples = examples,    # 示例库\n",
    "    embeddings = embeddings,  # 嵌入模型\n",
    "    vectorstore_cls = Chroma,   # 向量存储类型\n",
    "    k = 1  # 选择最相似的 k 个示例\n",
    ")\n",
    "\n",
    "similar_prompt = FewShotPromptTemplate(\n",
    "    # We provide an ExampleSelector instead of examples.\n",
    "    example_selector = example_selector,\n",
    "    example_prompt = example_prompt,\n",
    "    prefix = \"请将输入的词转换为其反义词\",\n",
    "    suffix = \"输入: {adjective}\\n输出:\",\n",
    "    input_variables = [\"adjective\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "d6b94cf8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "请将输入的词转换为其反义词\n",
      "\n",
      "输入: happy\n",
      "输出: sad\n",
      "\n",
      "输入: worried\n",
      "输出:\n"
     ]
    }
   ],
   "source": [
    "# 输入的是 worried，所以与其语义最相近的应该是 happy/sad 这个示例\n",
    "filled_prompt = similar_prompt.format(adjective=\"worried\")\n",
    "print(filled_prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "2ffb0a1d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "请将输入的词转换为其反义词\n",
      "\n",
      "输入: enthusiastic\n",
      "输出: apathetic\n",
      "\n",
      "输入: passionate\n",
      "输出:\n"
     ]
    }
   ],
   "source": [
    "# 也可以动态添加示例\n",
    "similar_prompt.example_selector.add_example(\n",
    "    {\"input\": \"enthusiastic\", \"output\": \"apathetic\"}\n",
    ")\n",
    "print(similar_prompt.format(adjective=\"passionate\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e25c1f82",
   "metadata": {},
   "source": [
    "## MaxMarginalRelevanceExampleSelector 示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "90c6885a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.vectorstores import FAISS\n",
    "from langchain_core.example_selectors import (\n",
    "    MaxMarginalRelevanceExampleSelector,\n",
    "    SemanticSimilarityExampleSelector,\n",
    ")\n",
    "from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate\n",
    "from langchain_openai import OpenAIEmbeddings\n",
    "\n",
    "example_prompt = PromptTemplate(\n",
    "    input_variables=[\"input\", \"output\"],\n",
    "    template=\"输入: {input}\\n输出: {output}\",\n",
    ")\n",
    "\n",
    "# 示例样本\n",
    "examples = [\n",
    "    {\"input\": \"happy\", \"output\": \"sad\"},\n",
    "    {\"input\": \"tall\", \"output\": \"short\"},\n",
    "    {\"input\": \"energetic\", \"output\": \"lethargic\"},\n",
    "    {\"input\": \"sunny\", \"output\": \"gloomy\"},\n",
    "    {\"input\": \"windy\", \"output\": \"calm\"},\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "415bc552",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用 FAISS 向量数据库里需要安装\n",
    "%pip install faiss-cpu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "02fa8880",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 嵌入模型注释请参考上一小节的 SemanticSimilarityExampleSelector 示例\n",
    "embeddings = HuggingFaceEmbeddings(\n",
    "    model_name = \"../../embed_model/bge-large-zh-v1.5\",\n",
    "    model_kwargs = {\"device\": \"cpu\"},\n",
    "    encode_kwargs = {\"normalize_embeddings\": True}\n",
    ")\n",
    "\n",
    "example_selector = MaxMarginalRelevanceExampleSelector.from_examples(\n",
    "    examples = examples,    # 示例库\n",
    "    embeddings = embeddings,  # 嵌入模型\n",
    "    vectorstore_cls = FAISS,   # 向量存储类型\n",
    "    k = 2  # 选择最相似的 k 个示例\n",
    ")\n",
    "\n",
    "mmr_prompt = FewShotPromptTemplate(\n",
    "    example_selector = example_selector,    # 使用ExampleSelector代替examples.\n",
    "    example_prompt = example_prompt,\n",
    "    prefix = \"将输入的词转换为其反义词\",\n",
    "    suffix = \"输入: {adjective}\\n输出:\",\n",
    "    input_variables = [\"adjective\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "556315a5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "将输入的词转换为其反义词\n",
      "\n",
      "输入: happy\n",
      "输出: sad\n",
      "\n",
      "输入: energetic\n",
      "输出: lethargic\n",
      "\n",
      "输入: worried\n",
      "输出:\n"
     ]
    }
   ],
   "source": [
    "# Input is a feeling, so should select the happy/sad example as the first one\n",
    "print(mmr_prompt.format(adjective=\"worried\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d33a4d8a",
   "metadata": {},
   "source": [
    "以上代码均参考官方文档给出的示例，更多详细内容请参考：[example-selectors](https://python.langchain.com/docs/how_to/#example-selectors)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
